// Copyright (c) 2023 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package handler

import (
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	cfg "github.com/alibaba/higress/plugins/wasm-go/extensions/scai/config"
	"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
	"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
	"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
	"golang.org/x/crypto/pbkdf2"
)

type ErrDenied struct {
	msg    string
	denied func() types.Action
}

func (e *ErrDenied) Error() string {
	return e.msg
}

const DEFAULT_KEY_LENGTH = 8

func GenerateKey() ([]byte, error) {
	bytes := make([]byte, DEFAULT_KEY_LENGTH)
	_, err := rand.Reader.Read(bytes)
	if err != nil {
		return nil, errors.New("failed to generate random salt")
	}
	return bytes, nil
}

// jwt-auth 插件认证逻辑与 basic-auth 一致：
// - global_auth == true 开启全局生效：
//   - 若当前 domain/route 未配置 allow 列表，即未配置该插件：则在所有 consumers 中查找，如果找到则认证通过，否则认证失败 (1*)
//   - 若当前 domain/route 配置了该插件：则在 allow 列表中查找，如果找到则认证通过，否则认证失败
//
// - global_auth == false 非全局生效：(2*)
//   - 若当前 domain/route 未配置该插件：则直接放行
//   - 若当前 domain/route 配置了该插件：则在 allow 列表中查找，如果找到则认证通过，否则认证失败
//
// - global_auth 未设置：
//   - 若没有一个 domain/route 配置该插件：则遵循 (1*)
//   - 若有至少一个 domain/route 配置该插件：则遵循 (2*)
//
// https://github.com/alibaba/higress/blob/e09edff827b94fa5bcc149bbeadc905361100c2a/plugins/wasm-go/extensions/basic-auth/main.go#L191
func OnHTTPRequestHeaders(ctx wrapper.HttpContext, config cfg.ScaiTokenConfig, log wrapper.Log) types.Action {
	proxywasm.AddHttpRequestHeader("STP_AUTH_SYS", config.StpAuthSys)
	timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
	grantType := "client_credential"
	signatureData := grantType + config.AccessKeyId + timestamp
	signature, err := GeneratePBKDF2WithHmacSHA1(config.AccessKeySecret, signatureData)
	httpClient, err := Client(config)
	if err != nil {
		log.Info("请求失败: " + err.Error())
		return types.ActionContinue
	}
	reqPath := fmt.Sprintf("%s?grantType=%s&accessKeyId=%s&timestamp=%s&signature=%s", config.PassTokenPath, grantType, config.AccessKeyId, timestamp, signature)
	log.Info("GetAccessToken, PAAS请求网关入参: " + reqPath)

	err = httpClient.Get(reqPath, nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) {
		// 请求没有返回200状态码，进行处理
		if statusCode != http.StatusOK {
			log.Errorf("http call failed, status: %d", statusCode)
			proxywasm.SendHttpResponse(http.StatusInternalServerError, nil,
				[]byte("http call failed"), -1)
			return
		}
		// 打印响应的HTTP状态码和应答body
		log.Infof("GetAccessToken status: %d, response body: %s", statusCode, responseBody)
		// 解析JSON响应
		var response map[string]interface{}
		if err := json.Unmarshal(responseBody, &response); err != nil {
			log.Info("JSON解析失败: " + err.Error())
			return
		}

		// 检查响应代码
		code := fmt.Sprintf("%v", response["code"])
		resultData, ok := response["result"].(map[string]interface{})
		if code != "0" || !ok {
			log.Info("无效响应: code=" + code)
			return
		}

		// 提取accessToken
		if token, exists := resultData["accessToken"].(string); exists {
			proxywasm.AddHttpRequestHeader("X-IDaaS-TOKEN", token)
			log.Info("scai --- build token: " + token)
		}
		// 恢复原始请求流程，继续往下处理，才能正常转发给后端服务
		proxywasm.ResumeHttpRequest()
	})

	if err != nil {
		// 由于调用外部服务失败，放行请求，记录日志
		log.Errorf("Error occured while calling http, it seems cannot find the service cluster.")
		return types.ActionContinue
	} else {
		// 需要等待异步回调完成，返回Pause状态，可以被ResumeHttpRequest恢复
		return types.ActionPause
	}
}

func GeneratePBKDF2WithHmacSHA1(secret string, password string) (string, error) {
	// 生成随机盐值
	salt, err := GenerateKey()
	if err != nil {
		return "", err
	}

	// 将密钥(secret)与盐值组合
	combinedSalt := append(salt, []byte(secret)...)
	derivedKey := pbkdf2.Key(
		[]byte(password),
		combinedSalt,
		5000,     // 迭代次数（建议≥10000）
		32,       // 输出密钥长度（如32表示256位）
		sha1.New, // 使用SHA1作为哈希算法
	)
	combinedKey := append(salt, derivedKey...)
	return hex.EncodeToString(combinedKey), nil // 转为十六进制字符串
}

func Client(config cfg.ScaiTokenConfig) (wrapper.HttpClient, error) {
	serviceName := config.ServiceFQDN
	return wrapper.NewClusterClient(wrapper.FQDNCluster{
		FQDN: serviceName,
		Port: 443,
	}), nil
}
